package server;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Timer;

import common.Packet;

/**A nice simple server. It sends nw_ping, nw_connect_accpet and nw_newpeers_accept (that's a terrible name)
 * and listens for nw_connect, nw_pong and nw_newpeers messages.
 * If you connect, it responds and adds you too it's peer list.
 * If you pong, it updates it's peer list (with the time).
 * If you request peers, if returns a list of peers.
 * It sends a ping to all peers every minute.
 * If you don't reply to a ping for 3 minutes, you'll be removed.
 * @author David Johnstone
 *
 */
public class Server
{
	static ServerGUI gui;
	//static List<Client> clients; //now performed with a seperate object with synchronised methods
	static ClientList clientList;
	static Timer timer;
	static DatagramSocket socket;
	static final int SOCKET_PORT = 10240;
	static final int LIVE_TIME = 180000;
	
	/**
	 * @param args
	 */
	public static void main(String[] args)
	{
		//create gui
		gui = new ServerGUI();
		//initialise data structure to store peers
		clientList = new ClientList(gui);
		//create and start threads to ping clients and remove them when they don't respond
		CheckClientThread cct = new CheckClientThread();
		RemoveDeadClientsThread rdct = new RemoveDeadClientsThread();
		cct.start();
		rdct.start();
		//create socket
		try
		{
			socket = new DatagramSocket(SOCKET_PORT);
		} catch (SocketException e)
		{
			gui.writeLog("unable to listen on port " + SOCKET_PORT);
		}
		//wait and respond loop
		while(true)
		{
			try
			{
				byte[] receivedBytes = new byte[1024];
				DatagramPacket rp = new DatagramPacket(receivedBytes, receivedBytes.length);
				socket.receive(rp);
				InetAddress address = rp.getAddress();
				int port = rp.getPort();
				Packet receivedPacket = new Packet(receivedBytes);
				if(receivedPacket.isType(Packet.Type.nw_connect))
				{
					String con = address.getHostAddress() + ":" + port;
					gui.addConnection(con);
					gui.writeLog(con + " connected");
					clientList.addClient(address, port);
					sendPacket(new Packet(Packet.Type.nw_connect_accept), address, port);
				}
				else if(receivedPacket.isType(Packet.Type.nw_pong))
				{
					gui.writeLog("updated " + address.getHostAddress() + ":" + port + " time to die");
					clientList.updateTimeToDie(address.getHostAddress(), port, System.currentTimeMillis() + LIVE_TIME);
				}
				else if(receivedPacket.isType(Packet.Type.nw_newpeers))
				{
					gui.writeLog(address.getHostAddress() + ":" + port + " requested more peers");
					Packet responsePacket = new Packet(Packet.Type.nw_newpeers_accept);
					responsePacket.addHeader("peer_list", clientList.createRandomPeerList(20, address, port));
					sendPacket(responsePacket, address, port);
				}
				
			} catch (IOException e)
			{
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	/*private static String createRandomPeerList(int maxNumber)
	{
		StringBuilder list = new StringBuilder();
		//if more are wanted than what we've got, return them all
		if(maxNumber >= clients.size())
		{
			for (Client c : clients)
			{
				if(list.length() > 0)
				{
					list.append(';');
				}
				list.append(c.address + ":" + c.port);
			}
		}
		else //if the client list is larger than the clients we want to return
		{
			Random r = new Random(System.nanoTime());
			double p = maxNumber / clients.size(); //the probability of any single client being returned
			int inList = 0;
			while(inList < maxNumber) //while we need more clients (this will be fairly inefficient when the maxNumber is close to the list size, but since they should be fairly low, it shouldn't be too much of a problem)
			{
				for (Client c : clients) //go over the list of clients and randomly add some
				{
					if(!c.in && r.nextDouble() < p) //if the client hasn't been added, and the dice says we should
					{
						if(list.length() > 0)
						{
							list.append(';');
						}
						list.append(c.address + ":" + c.port);
						c.in = true;
					}
				}
			}
			for (Client c : clients) //unmark which clients were returned
			{
				c.in = false;
			}
		}
		return list.toString();
	}*/

	/*private static void updateTimeToDie(InetAddress address, int port, long dieAt)
	{
		for (Client c : clients)
		{
			if(c.address == address && c.port == port)
			{
				c.dieAt = dieAt;
			}
		}
	}*/

	/*private static synchronized void addClient(InetAddress address, int port)
	{
		clients.add(new Client(address, port, System.currentTimeMillis()));
	}*/

	

	public synchronized static void sendPacket(Packet packet, InetAddress address, int port)
	{
		byte[] bytes = packet.getBytes();
		DatagramPacket p = new DatagramPacket(bytes, bytes.length, address, port);
		try
		{
			socket.send(p);
		} catch (IOException e)
		{
			gui.writeLog("error while sending to " + address.getHostName() + ":" + port);
		}
	}

}
